#include<iostream>
#include<unistd.h>
#include<cstdio>
#include<assert.h>
#include<ctime>
#include<pthread.h>

using namespace std;

// // 多线程抢票
// // 加锁保护   注意上锁的范围，不要把无关的代码也放在临界区 全局锁/静态锁
// pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; // pthread_mutex_t 原生线程库提供的一个数据类型

// int tickets = 10000; 

// void* getTickets(void* args)
// {
//     (void) args;
//     while(true)
//     {
//         // 上锁
//         pthread_mutex_lock(&mtx);
//         // 临界区
//         if (tickets > 0)  // 1.判断的本质也是计算的一种，在CPU中
//         {
//             usleep(1000);
//             printf("%s: %d\n", (char*)args, tickets);
//             tickets--;  // 2. 也可能出现问题
//             // 解锁
//             pthread_mutex_unlock(&mtx);
//         }
//         else
//         {
//             pthread_mutex_unlock(&mtx); // 这里也得解锁，如果break后，就无法解锁，其他线程也无法访问该资源
//             break;
//         }

//         // 抢完票，起始还需要后续的动作
//         usleep(rand() % 2000);
//     }

//     return nullptr;
// }

// int main()
// {
//     pthread_t t1, t2, t3;
//     // 多线程抢票
//     pthread_create(&t1, nullptr, getTickets, nullptr);
//     pthread_create(&t1, nullptr, getTickets, nullptr);
//     pthread_create(&t1, nullptr, getTickets, nullptr);

//     pthread_join(t1, nullptr);
//     pthread_join(t2, nullptr);
//     pthread_join(t3, nullptr);


//     return 0;
// }






// // 加锁保护：加锁的时候，一定要保证加锁的力度，越小越好！
// // 加锁就是串行执行了吗？加锁了之后，线程在临界区中，是否会切换，会有问题吗？原子性的体现？
// // 1.加了锁之后一样会切换,但不会有问题.
// //   虽然被切换了,但是你是持有锁被切换的,所以其他抢票线程要执行临界区代码,也必须先申请锁,锁它(其他线程)是无法申请成功的.
// //   所以，也不会让其他线程进入临界区，就保证了临界区中数据一致性！
// //   我是一个线程，我不申请锁，就是单纯的访问临界资源！ ———— 错误的编码方式
// // 2.原子性：在没有持有锁的线程来看，对我最有意义的情况只有两种：
// //                  1.其他线程没有持有锁(什么都没做) 2.其他线程释放锁(做完)，此时我可以申请锁。
// // 3.加锁就是串行执行吗？ 是的，执行临界区代码一定是串行的！
// // 4.要访问临界资源，每一个线程都必须先申请锁，每一个线程都必须先看到同一把锁 && 访问它，锁本身就是一种共享资源？
// // 谁来保证锁的安全呢？    所以，为了保证锁的安全，申请和释放锁，必须是原子的！
// // 5.如何保证申请锁和释放锁是原子的？锁是如何实现的？ 自己保证！用一条交换的汇编指令。

// // 多线程抢票，使用局部锁
// int tickets = 10000;
// #define THREAD_NUM 5

// class ThreadData
// {
// public:
//     ThreadData(const string &n, pthread_mutex_t* pm)
//         :tname(n)
//         ,pmtx(pm)
//     {

//     }
// public:
//     string tname;
//     pthread_mutex_t* pmtx;
// };


// void* getTickets(void* args)
// {
//     ThreadData* td = (ThreadData*)args;
//     while(true)
//     {
//         // 上锁
//         int n = pthread_mutex_lock(td->pmtx); // 成功上锁返回0
//         assert(n == 0);
        
//         // 临界区
//         if (tickets > 0)  // 1.判断的本质也是计算的一种，在CPU中
//         {
//             usleep(1000);
//             printf("%s: %d\n", td->tname.c_str(), tickets);
//             tickets--;  // 2. 也可能出现问题
//             // 解锁
//             n = pthread_mutex_unlock(td->pmtx);
//             assert(n == 0);
//         }
//         else
//         {
//             n = pthread_mutex_unlock(td->pmtx); // 这里也得解锁，如果break后，就无法解锁，其他线程也无法访问该资源
//             assert(n == 0);
//             break;
//         }

//         // 抢完票，起始还需要后续的动作
//         usleep(rand() % 2000);
//     }

//     delete td;
//     return nullptr;
// }

// int main()
// {
//     time_t start = time(nullptr);

//     // 定义锁  局部锁
//     pthread_mutex_t mtx;
//     // 初始化锁
//     pthread_mutex_init(&mtx, nullptr);

//     // 创建线程
//     pthread_t t[THREAD_NUM];

//     // 多线程抢票
//     for (int i = 0; i < THREAD_NUM; i++)
//     {
//         string name = "thread ";
//         name += to_string(i+1);
//         ThreadData* td = new ThreadData(name, &mtx);
//         pthread_create(t + i, nullptr, getTickets, (void*)td);
//     }

//     // 线程等待
//     for (int i = 0; i < THREAD_NUM; i++)
//     {
//         pthread_join(t[i], nullptr);
//     }

//     // 销毁锁
//     pthread_mutex_destroy(&mtx);

//     time_t end = time(nullptr);

//     cout << "cast: " << (int)(end -start) << endl;
//     return 0;
// }






// 一把锁会出现死锁？
int tickets = 10000;
#define THREAD_NUM 5

class ThreadData
{
public:
    ThreadData(const string &n, pthread_mutex_t* pm)
        :tname(n)
        ,pmtx(pm)
    {

    }
public:
    string tname;
    pthread_mutex_t* pmtx;
};


void* getTickets(void* args)
{
    ThreadData* td = (ThreadData*)args;
    while(true)
    {
        // 上锁
        int n = pthread_mutex_lock(td->pmtx);
        assert(n == 0);
        
        // 临界区
        if (tickets > 0)
        {
            usleep(1000);
            printf("%s: %d\n", td->tname.c_str(), tickets);
            tickets--;
            // 解锁  写成了申请锁，就会出现死锁，你有锁，你再申请锁，你肯定失败，会被挂起，
            // 你是锁的持有者，你被刮起了，就出现死锁
            n = pthread_mutex_lock(td->pmtx);
            assert(n == 0);
        }
        else
        {
            n = pthread_mutex_unlock(td->pmtx); 
            assert(n == 0);
            break;
        }

    }

    delete td;
    return nullptr;
}

int main()
{
    // 定义锁  局部锁
    pthread_mutex_t mtx;
    // 初始化锁
    pthread_mutex_init(&mtx, nullptr);

    // 创建线程
    pthread_t t[THREAD_NUM];

    // 多线程抢票
    for (int i = 0; i < THREAD_NUM; i++)
    {
        string name = "thread ";
        name += to_string(i+1);
        ThreadData* td = new ThreadData(name, &mtx);
        pthread_create(t + i, nullptr, getTickets, (void*)td);
    }

    // 线程等待
    for (int i = 0; i < THREAD_NUM; i++)
    {
        pthread_join(t[i], nullptr);
    }

    // 销毁锁
    pthread_mutex_destroy(&mtx);
    return 0;
}